/*
 * hash_table.c - Hashtable
 * Clunix, cezanne@clunix.com, 2004.10
 * 
 * Copyright (c) 2004 by Clunix Inc. All Rights Reserved.
 */
#include "config.h"

#include "ecm.h"

/* Flags for the elements. This is currently unused. */
#define FLAGS_NONE     0 /* No flags */
#define FLAGS_NORMAL   0 /* Normal item. All user-inserted stuff is normal */
#define FLAGS_INTERNAL 1 /* The item is internal to the hash table */

/* Prototypes */
static ECM_INLINE void transpose(hashtable_t *p_ht, ecm_uint32 l_bucket, ecm_hashentry_t *p_entry);
static ECM_INLINE void move_to_front(hashtable_t *p_ht, ecm_uint32 l_bucket, ecm_hashentry_t *p_entry);
static ECM_INLINE void free_entry_chain(hashtable_t *p_ht, ecm_hashentry_t *p_entry);
static ECM_INLINE ecm_hashentry_t *search_in_bucket(hashtable_t *p_ht, ecm_uint32 l_bucket, ecm_hashkey_t *p_key, unsigned char i_heuristics);

static ECM_INLINE void hk_fill(ecm_hashkey_t *p_hk, int i_size, ecm_cptr_t p_key);
static ECM_INLINE ecm_hashentry_t *he_create(hashtable_t *p_ht, ecm_ptr_t p_data, ecm_size_t i_key_size, ecm_cptr_t p_key_data);
static ECM_INLINE void he_finalize(hashtable_t *p_ht, ecm_hashentry_t *p_he);
static void hashtable_rehash(hashtable_t *p_ht, unsigned int i_size);
static hashtable_t *hashtable_create(unsigned int i_size);

/* --- private methods --- */

/* Move p_entry one up in its list. */
static ECM_INLINE void
transpose(hashtable_t *p_ht, ecm_uint32 l_bucket, ecm_hashentry_t *p_entry)
{
	/*
	 *  __    __    __    __
	 * |A_|->|X_|->|Y_|->|B_|
	 *             /
	 * =>        p_entry
	 *  __    __/   __    __
	 * |A_|->|Y_|->|X_|->|B_|
	 */
	if (p_entry->p_prev) {
		/* Otherwise p_entry is already first. */
		ecm_hashentry_t *p_x = p_entry->p_prev;
		ecm_hashentry_t *p_a = p_x?p_x->p_prev:NULL;
		ecm_hashentry_t *p_b = p_entry->p_next;

		if (p_a) 
			p_a->p_next = p_entry;
		else /* This element is now placed first */
			p_ht->pp_entries[l_bucket] = p_entry;

		if (p_b)
			p_b->p_prev = p_x;
		if (p_x) {
			p_x->p_next = p_entry->p_next;
			p_x->p_prev = p_entry;
		}
		p_entry->p_next = p_x;
		p_entry->p_prev = p_a;
	}
}

/* Move p_entry first */
static ECM_INLINE void
move_to_front(hashtable_t *p_ht, ecm_uint32 l_bucket, ecm_hashentry_t *p_entry)
{
	/*	
	 *  __    __    __
	 * |A_|->|B_|->|X_|
	 *            /
	 * =>  p_entry
	 *  __/   __    __
	 * |X_|->|A_|->|B_|
	 */
	if (p_entry == p_ht->pp_entries[l_bucket])
		return;

	/* Link p_entry out of the list. */
	p_entry->p_prev->p_next = p_entry->p_next;
	if (p_entry->p_next)
		p_entry->p_next->p_prev = p_entry->p_prev;

	/* Place p_entry first */
	p_entry->p_next = p_ht->pp_entries[l_bucket];
	p_entry->p_prev = NULL;
	p_ht->pp_entries[l_bucket]->p_prev = p_entry;
	p_ht->pp_entries[l_bucket] = p_entry;
}

/* Search for an element in a bucket */
static ECM_INLINE ecm_hashentry_t *
search_in_bucket(hashtable_t *p_ht, ecm_uint32 l_bucket,
		 ecm_hashkey_t *p_key, unsigned char i_heuristics)
{
	ecm_hashentry_t		*p_e;

	for (p_e = p_ht->pp_entries[l_bucket]; p_e; p_e = p_e->p_next) {
		if ((p_e->key.i_size == p_key->i_size) &&
		    (memcmp(p_e->key.p_key, p_key->p_key, p_e->key.i_size) == 0)) {
			/* Matching entry found - Apply heuristics, if any */
			switch (i_heuristics) {
			case ECM_HEURISTICS_MOVE_TO_FRONT:
				move_to_front(p_ht, l_bucket, p_e);
				break;
			case ECM_HEURISTICS_TRANSPOSE:
				transpose(p_ht, l_bucket, p_e);
				break;
			default:
				break;
			}
			return p_e;
		}
	}
	return NULL;
}

/* Free a chain of entries (in a bucket) */
static ECM_INLINE void
free_entry_chain(hashtable_t *p_ht, ecm_hashentry_t *p_entry)
{
	ecm_hashentry_t		*p_e = p_entry;

	while (p_e) {
		ecm_hashentry_t *p_e_next = p_e->p_next;
		he_finalize(p_ht, p_e);
		p_e = p_e_next;
	}
}

/* Fill in the data to a existing hash key */
static ECM_INLINE void
hk_fill(ecm_hashkey_t *p_hk, int i_size, ecm_cptr_t p_key)
{
	p_hk->i_size = i_size;
	p_hk->p_key = (ecm_ptr_t)p_key;
}

/* Create an hash entry */
static ECM_INLINE ecm_hashentry_t *
he_create(hashtable_t *p_ht, ecm_ptr_t p_data,
	  ecm_size_t i_key_size, ecm_cptr_t p_key_data)
{
	ecm_hashentry_t		*p_he;

	/*
	 * An element like the following is allocated:
	 *        elem->p_key
	 *       /   elem->p_key->p_key_data
	 *  ____|___/________
	 * |elem|key|key data|
	 * |____|___|________|
	 *
	 * That is, the key and the key data is stored "inline" within the

	 * hash entry.
	 *
	 * This saves space since malloc only is called once and thus avoids
	 * some fragmentation. Thanks to Dru Lemley for this idea.
	 */
	if (!(p_he = (ecm_hashentry_t*)malloc(sizeof(ecm_hashentry_t) + i_key_size)))
		return NULL;

	p_he->p_data = p_data;
	p_he->p_next = NULL;
	p_he->p_prev = NULL;

	/* Create the key */
	p_he->key.i_size = i_key_size;
	p_he->key.p_key = (void*)(p_he+1);
	memcpy(p_he->key.p_key, p_key_data, i_key_size);
	
	return p_he;
}

/* Finalize (free) a hash entry */
static ECM_INLINE void
he_finalize(hashtable_t *p_ht, ecm_hashentry_t *p_he)
{
	free(p_he);
}

/* Insert an entry into the hash table */
static int
hashtable_insert(hashtable_t *p_ht,
		 ecm_ptr_t p_entry_data,
		 ecm_size_t i_key_size,
		 ecm_cptr_t p_key_data)
{
	ecm_hashentry_t *p_entry;
	ecm_uint32 l_key;
	ecm_hashkey_t key;

	hk_fill(&key, i_key_size, p_key_data);
	l_key = p_ht->fn_hash(&key) & p_ht->i_size_mask;
	if (search_in_bucket(p_ht, l_key, &key, 0)) {
		/* Don't insert if the key is already present. */
		return -1;
	}
	if (!(p_entry = he_create(p_ht, p_entry_data, i_key_size, p_key_data)))
		return -2;

	/* Rehash if the number of items inserted is too high. */
	if (p_ht->i_automatic_rehash && p_ht->i_items > 2 * p_ht->i_size) {
		hashtable_rehash(p_ht, 2 * p_ht->i_size);
		l_key = p_ht->fn_hash(&key) & p_ht->i_size_mask;
	}

	/* Place the entry first in the list. */
	p_entry->p_next = p_ht->pp_entries[l_key];
	p_entry->p_prev = NULL;
	if (p_ht->pp_entries[l_key])
		p_ht->pp_entries[l_key]->p_prev = p_entry;
	p_ht->pp_entries[l_key] = p_entry;
	p_ht->p_nr[l_key]++;

	ECMASSERT(p_ht->pp_entries[l_key] ?
		  p_ht->pp_entries[l_key]->p_prev == NULL : ECM_TRUE);

	p_ht->i_items++;

	return 0;
}

/* Get the first entry in an iteration */
ECM_INLINE void *
hashtable_first(hashtable_t *p_ht,
		ht_iterator_t *p_iterator, void **pp_key)
{
	/* Fill the iterator */
	p_iterator->p_entry = p_ht->pp_entries[0];
	p_iterator->i_curr_bucket = 0;

	/* Step until non-empty bucket */
	for (; (p_iterator->i_curr_bucket < p_ht->i_size) && !p_ht->pp_entries[p_iterator->i_curr_bucket];
		p_iterator->i_curr_bucket++);
	if (p_iterator->i_curr_bucket < p_ht->i_size)
		p_iterator->p_entry = p_ht->pp_entries[p_iterator->i_curr_bucket];

	if (p_iterator->p_entry) {
		p_iterator->p_next = p_iterator->p_entry->p_next;
		if (pp_key)
			*pp_key = p_iterator->p_entry->key.p_key;

		return p_iterator->p_entry->p_data;
	}

	p_iterator->p_next = NULL;
	if (pp_key)
		*pp_key = NULL;
	return NULL;
}

ECM_INLINE void *
hashtable_next(hashtable_t *p_ht,
	       ht_iterator_t *p_iterator, void **pp_key)
{
	if (p_iterator->p_next) {
		/* More entries in the current bucket */
		p_iterator->p_entry = p_iterator->p_next;
		p_iterator->p_next = p_iterator->p_next->p_next;

		if (pp_key)
			*pp_key = p_iterator->p_entry->key.p_key;
		return p_iterator->p_entry->p_data;
	}
	else {
		p_iterator->p_entry = NULL;
		p_iterator->i_curr_bucket++;
	}

	/* Step until non-empty bucket */
	for (; (p_iterator->i_curr_bucket < p_ht->i_size) && !p_ht->pp_entries[p_iterator->i_curr_bucket];
		p_iterator->i_curr_bucket++);

	/* FIXME: Add someplace here:
	*  if (p_iterator->p_entry->i_flags & FLAGS_INTERNAL)
	*     return hashtable_next(p_ht, p_iterator);
	*/
	if (p_iterator->i_curr_bucket < p_ht->i_size) {
		p_iterator->p_entry = p_ht->pp_entries[p_iterator->i_curr_bucket];

		if (p_iterator->p_entry)
			p_iterator->p_next = p_iterator->p_entry->p_next;

		if (pp_key)
			*pp_key = p_iterator->p_entry->key.p_key;
		return p_iterator->p_entry->p_data;
	}

	/* Last entry */
	p_iterator->i_curr_bucket = 0;
	p_iterator->p_entry = NULL;
	p_iterator->p_next = NULL;

	if (pp_key)
		*pp_key = NULL;
	return NULL;
}

static void
hashtable_rehash(hashtable_t *p_ht, unsigned int i_size)
{
	hashtable_t *p_tmp;
	ht_iterator_t iterator;
	void *p_key;
	void *p;
	unsigned int	i;

	/* Recreate the hash table with the new size */
	p_tmp = hashtable_create(i_size);

	/* Set the flags for the new hash table */
	ecm_hashtable_sethash(p_tmp, p_ht->fn_hash);
	ecm_hashtable_set_heuristics(p_tmp, 0);
	ecm_hashtable_set_autorehash(p_tmp, ECM_FALSE);

	/* Walk through all elements in the table and insert them into the temporary one. */
	for (p = hashtable_first(p_ht, &iterator, &p_key); p;
	     p = hashtable_next(p_ht, &iterator, &p_key)) {
		ECMASSERT(iterator.p_entry != NULL);

		/* Insert the entry into the new table */
		hashtable_insert(p_tmp,
				 iterator.p_entry->p_data,
				 iterator.p_entry->key.i_size,
				 iterator.p_entry->key.p_key);
	}

	/* Remove the old table... */
	for (i = 0; i < p_ht->i_size; i++) {
		if (p_ht->pp_entries[i]) {
			/* Delete the entries in the bucket */
			free_entry_chain (p_ht, p_ht->pp_entries[i]);
			p_ht->pp_entries[i] = NULL;
		}
	}

	free(p_ht->pp_entries);
	free(p_ht->p_nr);

	/* ... and replace it with the new */
	p_ht->i_size = p_tmp->i_size;
	p_ht->i_size_mask = p_tmp->i_size_mask;
	p_ht->i_items = p_tmp->i_items;
	p_ht->pp_entries = p_tmp->pp_entries;
	p_ht->p_nr = p_tmp->p_nr;

	/* Clean up */
	p_tmp->pp_entries = NULL;
	p_tmp->p_nr = NULL;
	free(p_tmp);
}

/* --- Exported methods --- */
/* Create a new hash table */
static hashtable_t *
hashtable_create(unsigned int i_size)
{
	hashtable_t		*p_ht;
	int			i = 0;

	p_ht = (hashtable_t*)malloc(sizeof(hashtable_t));

	/* Set the size of the hash table to the nearest 2^i higher
	   then i_size */
	p_ht->i_size = 0;
	while (p_ht->i_size < i_size)
		p_ht->i_size = 1 << i++;

	p_ht->i_size_mask = (1 << (i-1)) - 1; /* Mask to & with */
	p_ht->i_items = 0;

	p_ht->fn_hash = ecm_one_at_a_time_hash;

	/* Set flags */
	p_ht->i_heuristics = ECM_HEURISTICS_NONE;
	p_ht->i_automatic_rehash = ECM_TRUE;

	/* Create an empty bucket list. */
	p_ht->pp_entries = (ecm_hashentry_t **)malloc(p_ht->i_size * sizeof(ecm_hashentry_t*));

	memset(p_ht->pp_entries, 0, p_ht->i_size * sizeof(ecm_hashentry_t *));

	p_ht->p_nr = (int *)malloc(p_ht->i_size*sizeof(int));
	memset(p_ht->p_nr, 0, p_ht->i_size * sizeof(int));
	p_ht->lock = ecm_mutex_create(ECM_FALSE);

	return p_ht;
}

ecm_hashtable_t
ecm_hashtable_create(ecm_uint32 i_size)
{
	hashtable_t	*p_ht = hashtable_create(i_size);
	ECM_MEM_TRACE(ECM_TRUE, ECM_MTRINFO_HASHTABLE, p_ht, 0);
	return p_ht;
}

/* Set the hash function to use */
void
ecm_hashtable_sethash(ecm_hashtable_t ht, ecm_fn_hash_t fn_hash)
{
	hashtable_t	*p_ht = (hashtable_t *)ht;
	if (p_ht == NULL)
		return;

	ecm_mutex_lock(p_ht->lock);
	p_ht->fn_hash = fn_hash;
	ecm_mutex_unlock(p_ht->lock);
}

/* Set the heuristics to use. */
void
ecm_hashtable_set_heuristics(ecm_hashtable_t ht, int i_heuristics)
{
	hashtable_t	*p_ht = (hashtable_t *)ht;

	if (p_ht == NULL)
		return;

	ecm_mutex_lock(p_ht->lock);
	p_ht->i_heuristics = i_heuristics;
	ecm_mutex_unlock(p_ht->lock);
}

/* Set the rehashing status of the table. */
void
ecm_hashtable_set_autorehash(ecm_hashtable_t ht, ECMBOOL b_rehash)
{
	hashtable_t	*p_ht = (hashtable_t *)ht;

	if (p_ht == NULL)
		return;

	ecm_mutex_lock(p_ht->lock);
	p_ht->i_automatic_rehash = b_rehash;
	ecm_mutex_unlock(p_ht->lock);
}

/* Get the number of items in the hash table */
ecm_int32
ecm_hashtable_getsize(ecm_hashtable_t ht)
{
	hashtable_t	*p_ht = (hashtable_t *)ht;

	if (p_ht == NULL)
		return -1;
	return (ecm_int32)p_ht->i_items;
}

/* Get the size of the hash table */
ecm_uint32
ecm_hashtable_get_tablesize(ecm_hashtable_t ht)
{
	hashtable_t	*p_ht = (hashtable_t *)ht;

	if (p_ht == NULL)
		return -1;
	return (ecm_uint32)p_ht->i_size;
}

/* Insert an entry into the hash table */
ECMBOOL
ecm_hashtable_insert(ecm_hashtable_t ht,
		     ecm_ptr_t p_entry_data,
		     ecm_size_t i_key_size,
		     ecm_cptr_t p_key_data)
{
	hashtable_t	*p_ht = (hashtable_t *)ht;
	int	ret;

	if (p_ht == NULL)
		return ECM_FALSE;

	ecm_mutex_lock(p_ht->lock);
	ret = hashtable_insert(p_ht, p_entry_data, i_key_size, p_key_data);
	ecm_mutex_unlock(p_ht->lock);

	if (ret < 0)
		return ECM_FALSE;
	return ECM_TRUE;
}

ecm_ptr_t
ecm_hashtable_get(ecm_hashtable_t ht,
		  ecm_size_t i_key_size,
		  ecm_cptr_t p_key_data)
{
	hashtable_t	*p_ht = (hashtable_t *)ht;
	ecm_hashentry_t	*p_e;
	ecm_hashkey_t	key;
	ecm_uint32	l_key;
	void		*data;

	if (p_ht == NULL)
		return NULL;

	ecm_mutex_lock(p_ht->lock);
	hk_fill(&key, i_key_size, p_key_data);

	l_key = p_ht->fn_hash(&key) & p_ht->i_size_mask;

	/* Check that the first element in the list really is the first. */
	ECMASSERT(p_ht->pp_entries[l_key] ? 
		  p_ht->pp_entries[l_key]->p_prev == NULL : ECM_TRUE);

	p_e = search_in_bucket(p_ht, l_key, &key, p_ht->i_heuristics);
	data = p_e ? p_e->p_data : NULL;
	ecm_mutex_unlock(p_ht->lock);

	return data;
}

/* Replace an entry from the hash table. The entry is returned, or NULL if it wasn't found */
ecm_ptr_t
ecm_hashtable_replace(ecm_hashtable_t ht,
		      void *p_entry_data,
		      ecm_size_t i_key_size, ecm_cptr_t p_key_data)
{
	hashtable_t	*p_ht = (hashtable_t *)ht;
	ecm_hashentry_t		*p_e;
	ecm_hashkey_t		key;
	ecm_uint32		l_key;
	void			*p_old = NULL;

	if (p_ht == NULL)
		return NULL;

	ecm_mutex_lock(p_ht->lock);
	hk_fill(&key, i_key_size, p_key_data);

	l_key = p_ht->fn_hash(&key) & p_ht->i_size_mask;

	/* Check that the first element in the list really is the first. */
	ECMASSERT(p_ht->pp_entries[l_key] ? 
		  p_ht->pp_entries[l_key]->p_prev == NULL : ECM_TRUE);

	p_e = search_in_bucket(p_ht, l_key, &key, p_ht->i_heuristics);

	if (p_e) {
		p_old = p_e->p_data;
		p_e->p_data = p_entry_data;
	}
	ecm_mutex_unlock(p_ht->lock);

	return p_old;
}

/* Remove an entry from the hash table. The removed entry, or NULL, is
   returned (and NOT free'd). */
ecm_ptr_t
ecm_hashtable_remove(ecm_hashtable_t ht,
		     ecm_size_t i_key_size,
		     ecm_cptr_t p_key_data)
{
	hashtable_t	*p_ht = (hashtable_t *)ht;
	ecm_hashentry_t *p_out;
	ecm_hashkey_t	key;
	ecm_uint32	l_key;
	void *p_ret = NULL;

	if (p_ht == NULL)
		return NULL;

	ecm_mutex_lock(p_ht->lock);
	hk_fill(&key, i_key_size, p_key_data);
	l_key = p_ht->fn_hash(&key)&p_ht->i_size_mask;

	/* Check that the first element really is the first */
	ECMASSERT((p_ht->pp_entries[l_key] ? 
		   p_ht->pp_entries[l_key]->p_prev == NULL : ECM_TRUE));

	p_out = search_in_bucket(p_ht, l_key, &key, 0);

	/* Link p_out out of the list. */
	if (p_out) {
		if (p_out->p_prev)
			p_out->p_prev->p_next = p_out->p_next;
		else /* first in list */
			p_ht->pp_entries[l_key] = p_out->p_next;
		if (p_out->p_next)
			p_out->p_next->p_prev = p_out->p_prev;

		/* This should ONLY be done for normal items
		 * (for now all items) */
		p_ht->i_items--;

		p_ht->p_nr[l_key]--;

		p_ret = p_out->p_data;
		he_finalize(p_ht, p_out);
	}
	ecm_mutex_unlock(p_ht->lock);

	return p_ret;
}

/* Get the first entry in an iteration */
ecm_ptr_t
ecm_hashtable_first(ecm_hashtable_t ht, ecm_ht_iterator_t *p_iter, void **pp_key)
{
	hashtable_t	*p_ht = (hashtable_t *)ht;
	ht_iterator_t	*p_iterator = (ht_iterator_t *)p_iter;
	ecm_ptr_t	data;

	if (p_ht == NULL || p_iterator == NULL)
		return NULL;

	ecm_mutex_lock(p_ht->lock);
	data = hashtable_first(p_ht, p_iterator, pp_key);
	ecm_mutex_unlock(p_ht->lock);

	return data;
}

/* Get the next entry in an iteration. You have to call ecm_hashtable_first
   once initially before you use this function */
ecm_ptr_t
ecm_hashtable_next(ecm_hashtable_t ht, ecm_ht_iterator_t *p_iter, ecm_ptr_t *pp_key)
{
	hashtable_t	*p_ht = (hashtable_t *)ht;
	ht_iterator_t	*p_iterator = (ht_iterator_t *)p_iter;
	ecm_ptr_t	data;

	if (p_ht == NULL || p_iterator == NULL)
		return NULL;

	ecm_mutex_lock(p_ht->lock);
	data = hashtable_next(p_ht, p_iterator, pp_key);
	ecm_mutex_unlock(p_ht->lock);

	return data;
}

void
ecm_hashtable_clear(ecm_hashtable_t ht)
{
	hashtable_t	*p_ht = (hashtable_t *)ht;
	unsigned int	i;

	if (p_ht == NULL)
		return;

	ecm_mutex_lock(p_ht->lock);

	if (p_ht->pp_entries) {
		/* For each bucket, free all entries */
		for (i = 0; i < p_ht->i_size; i++) {
			free_entry_chain(p_ht, p_ht->pp_entries[i]);
			p_ht->pp_entries[i] = NULL;
		}
	}
	if (p_ht->p_nr)
		memset(p_ht->p_nr, 0, p_ht->i_size*sizeof(int));
	p_ht->i_items = 0;
	ecm_mutex_unlock(p_ht->lock);
}

/* Finalize (free) a hash table */
void
ecm_hashtable_finalize(ecm_hashtable_t ht)
{
	hashtable_t	*p_ht = (hashtable_t *)ht;
	if (p_ht == NULL)
		return;

	ECM_MEM_TRACE(ECM_FALSE, ECM_MTRINFO_HASHTABLE, p_ht, 0);

	ecm_hashtable_clear(p_ht);
	ecm_mutex_free(p_ht->lock);
	free(p_ht->pp_entries);
	free(p_ht->p_nr);
	free(p_ht);
}

/* Rehash the hash table (i.e. change its size and reinsert all
 * items). This operation is slow and should not be used frequently.
 */
void
ecm_hashtable_rehash(ecm_hashtable_t ht, unsigned int i_size)
{
	hashtable_t	*p_ht = (hashtable_t *)ht;
	if (p_ht == NULL)
		return;

	ecm_mutex_lock(p_ht->lock);
	hashtable_rehash(p_ht, i_size);
	ecm_mutex_unlock(p_ht->lock);
}

ecm_ptr_t
ecm_hashtable_str_get(ecm_hashtable_t ht, const char *key)
{
	int	len;

	if (ecm_empty_str(key))
		return NULL;
	len = ecm_strlen(key);
	return ecm_hashtable_get(ht, len + 1, (ecm_cptr_t)key);
}

ECMBOOL
ecm_hashtable_str_insert(ecm_hashtable_t ht, ecm_ptr_t p_data, const char *key)
{
	int	len;

	if (ecm_empty_str(key))
		return ECM_FALSE;
	len = ecm_strlen(key);
	return ecm_hashtable_insert(ht, p_data, len + 1, (ecm_cptr_t)key);
}

ecm_ptr_t
ecm_hashtable_str_replace(ecm_hashtable_t ht, ecm_ptr_t p_data, const char *key)
{
	int	len;

	if (ecm_empty_str(key))
		return NULL;
	len = ecm_strlen(key);
	return ecm_hashtable_replace(ht, p_data, len + 1, (ecm_cptr_t)key);
}

ecm_ptr_t
ecm_hashtable_str_remove(ecm_hashtable_t ht, const char *key)
{
	int	len;

	if (ecm_empty_str(key))
		return NULL;
	len = ecm_strlen(key);
	return ecm_hashtable_remove(ht, len + 1, (ecm_cptr_t)key);
}

ecm_ptr_t
ecm_hashtable_str_first(ecm_hashtable_t ht,
			ecm_ht_iterator_t *p_iterator,
			const char **pkey)
{
	return ecm_hashtable_first(ht, p_iterator, (void **)pkey);
}

ecm_ptr_t
ecm_hashtable_str_next(ecm_hashtable_t ht,
		       ecm_ht_iterator_t *p_iterator,
		       const char **pkey)
{
	return ecm_hashtable_next(ht, p_iterator, (void **)pkey);
}

#ifdef ECM_DEBUG

ecm_hashtable_t
ecm_hashtable_create_internal(void)
{
	hashtable_t	*ht = hashtable_create(32);
	ecm_hashtable_set_autorehash(ht, ECM_TRUE);
	return ht;
}

void
ecm_hashtable_finalize_internal(ecm_hashtable_t ht)
{
	hashtable_t	*p_ht = (hashtable_t *)ht;
	ecm_hashtable_clear(p_ht);
	ecm_mutex_free(p_ht->lock);
	free(p_ht->pp_entries);
	free(p_ht);
}

#endif
